﻿using System;
using System.Linq;
using Notoric.Commanding.CommandBase;
using Notoric.Commanding;

namespace Notoric.ViewModel
{
    /// <summary>
    /// An undoable command for inserting a new clef into a staff.
    /// </summary>
    public class InsertClefCommand : UndoableCommandBase
    {
        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="InsertClefCommand"/> class.
        /// </summary>
        /// <param name="undoStack">The undo command stack</param>
        /// <param name="redoStack">The redo command stack</param>
        public InsertClefCommand(CommandStack undoStack, CommandStack redoStack)
            : base(undoStack, redoStack)
        {
            AddParameter<ushort>("Beat", ParameterDirection.In);
            AddParameter<Elements.CWNotation.ClefType>("ClefType", ParameterDirection.In);
            AddParameter<Model.Elements.MeasureElement>("Measure", ParameterDirection.In);
            AddParameter<Elements.CWNotation.Staff>("Staff", ParameterDirection.In);
            AddParameter<int>("StaffPosition", ParameterDirection.In);

            AddParameter<Elements.CWNotation.Clef>("CreatedClef", ParameterDirection.Out);

            Name = "Insert Clef";
        }

        #endregion

        #region Command Logic

        /// <inheritdoc/>
        protected override void CommandOperation(object parameter)
        {
            var modelStaff = (Model.Elements.CWNotation.Staff)Staff.AdaptedElement;
            var content = Measure[modelStaff.Descriptor.FindOwner<Model.Elements.PartDescriptorElement>()].Content;
            var beat = content.Beats.FirstOrDefault(b => b.Beginning == Beat);
            if (beat == null)
                beat = content.AddBeat(Beat);

            var modelClef = modelStaff.Symbols.FirstOrDefault(s => s is Model.Elements.CWNotation.Clef && s.Measure == Measure && s.Beat == s.Beat) as Model.Elements.CWNotation.Clef;
            if (modelClef == null)
            {
                modelClef = modelStaff.AddClef(beat);
                modelClef.StaffPosition = StaffPosition;
                modelClef.Type = (Model.Elements.CWNotation.ClefType)Enum.Parse(typeof(Model.Elements.CWNotation.ClefType), ClefType.ToString());
                if (beat.Beginning != 0)
                    modelClef.TypeSize = modelStaff.Height - 0.1;
                
                CreatedClef = Staff.Symbols.First(s => s.AdaptedElement == modelClef) as Elements.CWNotation.Clef;
            }
            else
            {
                CreatedClef = Staff.Symbols.First(s => s.AdaptedElement == modelClef) as Elements.CWNotation.Clef;
                var macro = new UndoableMacroCommand(CreatedClef.UndoStack, CreatedClef.RedoStack);

                macro.Commands.Add(CreatedClef.CreateSetStaffPositionCommand(StaffPosition));
                macro.Commands.Add(CreatedClef.CreateSetTypeCommand(ClefType));
                existingClefCommand = macro;
                existingClefCommand.Execute(null);

            }


            if (modelStaff.FindOwner<Model.Elements.PageElement>() != null)
                Model.Elements.CWNotation.Layout.Layouter.LayoutHorizontally(modelStaff.FindOwner<Model.Elements.CWNotation.System>());
            else
                Model.Elements.CWNotation.Layout.Layouter.LayoutHorizontallyNA(modelStaff.FindOwner<Model.Elements.CWNotation.System>());

            Staff.FindOwner<Elements.Project>().IsModified = true;
        }

        /// <inheritdoc/>
        protected override void RedoOperation()
        {
            var modelStaff = (Model.Elements.CWNotation.Staff)Staff.AdaptedElement;

            if (existingClefCommand == null)
            {
                modelStaff.RestoreClef(CreatedClef.AdaptedElement as Model.Elements.CWNotation.Clef);
                var vmClef = Staff.Symbols.First(s => s.AdaptedElement == CreatedClef.AdaptedElement) as Elements.CWNotation.Clef;
                Staff.Symbols[Staff.Symbols.IndexOf(vmClef)] = CreatedClef;
            }
            else
                existingClefCommand.Execute(null);

            if (modelStaff.FindOwner<Model.Elements.PageElement>() != null)
                Model.Elements.CWNotation.Layout.Layouter.LayoutHorizontally(modelStaff.FindOwner<Model.Elements.CWNotation.System>());
            else
                Model.Elements.CWNotation.Layout.Layouter.LayoutHorizontallyNA(modelStaff.FindOwner<Model.Elements.CWNotation.System>());

            Staff.FindOwner<Elements.Project>().IsModified = true;
        }

        /// <inheritdoc/>
        protected override void UndoOperation()
        {
            var modelStaff = (Model.Elements.CWNotation.Staff)Staff.AdaptedElement;

            if (existingClefCommand == null)
                modelStaff.RemoveClef(CreatedClef.AdaptedElement as Model.Elements.CWNotation.Clef);
            else
                existingClefCommand.Execute(null);

            if (modelStaff.FindOwner<Model.Elements.PageElement>() != null)
                Model.Elements.CWNotation.Layout.Layouter.LayoutHorizontally(modelStaff.FindOwner<Model.Elements.CWNotation.System>());
            else
                Model.Elements.CWNotation.Layout.Layouter.LayoutHorizontallyNA(modelStaff.FindOwner<Model.Elements.CWNotation.System>());

            Staff.FindOwner<Elements.Project>().IsModified = true;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the beat to which the clef is inserted.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="UInt16"/>
        /// </para>
        /// <para>
        /// The beat to which the clef is inserted.
        /// </para>
        /// </value>
        public ushort Beat
        {
            get { return GetParameterValue<ushort>("Beat"); }
            set { SetParameterValue<ushort>("Beat", value); }
        }

        /// <summary>
        /// Gets or sets the type of the clef.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Elements.CWNotation.ClefType"/>
        /// </para>
        /// <para>
        /// The type of the clef.<br/>
        /// This property wraps the "ClefType" command parameter.
        /// </para>
        /// </value>
        public Elements.CWNotation.ClefType ClefType
        {
            get { return GetParameterValue<Elements.CWNotation.ClefType>("ClefType"); }
            set { SetParameterValue<Elements.CWNotation.ClefType>("ClefType", value); }
        }

        /// <summary>
        /// Gets the created clef.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Elements.CWNotation.Clef"/>
        /// </para>
        /// <para>
        /// The created clef.<br/>
        /// This property wraps the "CreatedClef" command parameter.
        /// </para>
        /// </value>
        public Elements.CWNotation.Clef CreatedClef
        {
            get { return GetParameterValue<Elements.CWNotation.Clef>("CreatedClef"); }
            private set { SetParameterValue<Elements.CWNotation.Clef>("CreatedClef", value); }
        }

        /// <summary>
        /// Gets or sets the measure to which the clef is inserted.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Model.Elements.MeasureElement"/>
        /// </para>
        /// <para>
        /// The measure to which the clef is inserted.<br/>
        /// This property wraps the "Measure" command parameter.
        /// </para>
        /// </value>
        public Model.Elements.MeasureElement Measure
        {
            get { return GetParameterValue<Model.Elements.MeasureElement>("Measure"); }
            set { SetParameterValue<Model.Elements.MeasureElement>("Measure", value); }
        }

        /// <summary>
        /// Gets or sets the staff to which the clef is inserted.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Elements.CWNotation.Staff"/>
        /// </para>
        /// <para>
        /// The staff to which the clef is inserted.<br/>
        /// This property wraps the "Staff" command parameter.
        /// </para>
        /// </value>
        public Elements.CWNotation.Staff Staff
        {
            get { return GetParameterValue<Elements.CWNotation.Staff>("Staff"); }
            set { SetParameterValue<Elements.CWNotation.Staff>("Staff", value); }
        }

        /// <summary>
        /// Gets or sets the staff position to which the clef is inserted.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Int32"/>
        /// </para>
        /// <para>
        /// The staff position to which the clef is inserted.<br/>
        /// This property wraps the "StaffPosition" command parameter.
        /// </para>
        /// </value>
        public int StaffPosition
        {
            get { return GetParameterValue<int>("StaffPosition"); }
            set { SetParameterValue<int>("StaffPosition", value); }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// If the specified position already contained a clef,
        /// this command was used to change its properties.
        /// Otherwise this field is <see langword="null"/>.
        /// </summary>
        private UndoableCommandBase existingClefCommand;

        #endregion
    }
}
